/*
 *     Heart of Gold engine management system
    Copyright (C) 2004 Heart of Gold development team

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 * 
 * @author Greg Feigenson
 * @author Ryan Higgins
 * @version %I%, %G%
 *
 * Spark controller for the Heart of Gold system.
 * Contains a JPanel for display within the main menu.
 *
 * On a message: we should update the Advance Table, or
 * the dwell.
 *
 * So far we are not dealing with a multiCoil system. If we
 * were, we'd need to add more state/logic.
 */

/** Advance Table
 * -------------
 * Y Axis: MAP (Manifold Absolute Pressure)
 * X Axis: RPM (Rotations Per Minute)
 *
 * "Scale"
 * -------
 * Labels the axies of the graph so you know what the RPM or MAP is at any point
 * so that at (3200RPM,10MAP) you'd get "12", where 12 is the degrees
 */

package com.heartOfGold.controller;

import java.io.IOException;
import java.util.Iterator;
import java.util.Stack;
import java.util.LinkedList;

import com.heartOfGold.model.Message;
import com.heartOfGold.model.ModelMessage;
import com.heartOfGold.model.megasquirtECU.MegasquirtMessenger;
import com.heartOfGold.view.UiSpark;

//import java.util.ListIterator;

public class Spark extends Component {
    // FIXME: clean up code, add sorting to outgoing
    // list, homogenize data structures

    /* Constructor */
    public Spark() throws IOException {
        id = "PSST";
        panelLabel = "Spark Controller";
        panel = new UiSpark(this);
        // isMultiCoil = false;
        rpmscale = new double[8];
        mapscale = new int[8];
        advanceTable = new double[8][8];
        // dwell = 0;
        messenger = MegasquirtMessenger.getMessenger();
        tableRequested = false;
    }

    // Public Utility Methods
    public final double cylinderCount() {
        return cylinderCount;
    }

    public final void setCylinders(final int i) {
        cylinderCount = i;
    }

    // Overrides to abstract parent class
    public final void parseMessage(final MegasquirtMessenger sender,
            final Message m) {
        if (tableRequested) {
            // $PSST,ROW,MAP,Val1,Val2...Val8*CHKSM
            Iterator<Object> it = m.getArgs();
            int row = stringToInt((String) it.next());
            int i = 0;
            if (row == 8) {
                it.next(); // there should never be a MAP value for the RPM row
                // $PSST,ROW,0,Val1,Val2...Val8*CHKSM
                while (it.hasNext()) {
                    rpmscale[i] = tacMSToRPM(stringToDouble((String) it.next()));
                    i++;
                }
                try {
                    messenger.sendMessage((Message) outStack.pop());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (row < 8) {
                mapscale[row] = stringToInt((String) it.next());
                while (it.hasNext()) {
                    // SC sends us data in ms, need degrees
                    advanceTable[(int) row][(int) i] = (double) microsecondsToDegrees(
                            stringToDouble((String) it.next()),
                            rpmscale[(int) i]);
                    i++;
                }
                if (outStack.empty()) {
                    // no longer requesting table, data has been read
                    tableRequested = false;
                    UiSpark
                            .syncWithModel(mapscale, rpmscale, advanceTable);
                } else {
                    try {
                        messenger.sendMessage((Message) outStack.pop());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } else {
            if (checkMessage(m)) {
                list.removeFirst();
                if (list.size() > 0) {
                    try {
                        messenger.sendMessage((Message) list.getFirst());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                try {
                    messenger.sendMessage((Message) list.getFirst());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public final void update(final MegasquirtMessenger sender, final Message m) {
        if (id.equals(m.getID())) {
            parseMessage(sender, m);
        }
        return;
    }

    // Messaging
    private boolean checkMessage(final Message incomingMessage) {
        return ((Message) list.getFirst()).equals(incomingMessage);
    }

    public final void sendTable() {
        list.clear(); // clean list of garbage values
        for (int a = 0; a < 8; a++) {
            int[] row = formatRowforMicroseconds(a);
            list.add(makeRowMessage(row, a));
        }
        int[] doubles = formatTacRowforRPM();
        list.add(makeRPMRowMessage(doubles));
        try {
            messenger.sendMessage((Message) list.getFirst());
        } catch (IOException e) {
            e.printStackTrace();
        }
        for (double i = 0; i < list.size(); i++) {
            System.out.println("List element " + i + " " + list.get((int) i));
        }
    }

    public final void syncWithUI(final int[] map, final double[] rpm,
            final double[][] table) {
        rpmscale = rpm;
        mapscale = map;
        advanceTable = table;
    }

    // NMEA0183->$PSGT,y*CHKSM
    public final void askForTable() {
        outStack.clear(); // flush the stack
        tableRequested = true;
        for (int i = 0; i <= 8; i++) {
            Message message = new Message("PSGT");
            message.addArg(intToString(i));
            outStack.addElement(message);
        }
        try {
            messenger.sendMessage((Message) outStack.pop());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private ModelMessage makeRowMessage(final int[] row, final int rowNum) {
        // NMEA1083 -> $PSST,RowNum,MAPValue,Table1,Table2...Table8*CHKSM
        Message message = new Message("PSST");
        message.addArg(intToString(rowNum));
        message.addArg(intToString(mapscale[rowNum]));
        for (int a = 0; a < row.length; a++) {
            message.addArg((new Integer(row[a])).toString());
        }
        return message;
    }

    private ModelMessage makeRPMRowMessage(final int[] row) {
        // NMEA0183 -> $PSST,,RPMVal1,RPMVal2...RPMVal8*CHKSM
        Message message = new Message("PSST");
        message.addArg("8");
        message.addArg("0");
        for (int a = 0; a < row.length; a++) {
            message.addArg((new Integer(row[a])).toString());
        }
        return message;
    }

    // Metric/Data type conversion functions

    private double microsecondsToDegrees(final double microSeconds,
            final double rpm) {
        return (microSeconds * rpm) / DEGTOMSFACTOR;
    }

    private double degreesToMicroseconds(final double degree, final double rpm) {
        return DEGTOMSFACTOR * degree / rpm;
    }

    private int stringToInt(final String str) {
        if (str == null || str.equals("")) {
            return 0;
        }
        return (new Integer(str).intValue());
    }

    private double stringToDouble(final String str) {
        if (str == null || str.equals("")) {
            return 0;
        }
        return (new Double(str).doubleValue());
    }

    private String intToString(final int i) {
        return new Integer(i).toString();
    }

    /*
     * private String doubleToString(double d) { return new
     * Double(d).toString(); }
     */

    private int[] formatRowforMicroseconds(final int x) {
        int[] row = new int[rpmscale.length];
        int y;
        for (y = 0; y < row.length; y++) {
            row[y] = (int) degreesToMicroseconds(advanceTable[x][y],
                    rpmscale[y]);
        }
        return row;
    }

    /*
     * private double[] formatRowforDegrees(int x) { double[] row = new
     * double[rpmscale.length]; int y; for (y = 0; y < row.length; y++) {
     * row[y]= microsecondsToDegrees(advanceTable[x][y], rpmscale[y]); } return
     * row; }
     */

    /*
     * private double[] formatRPMRowforTac() { double[] row = new
     * double[rpmscale.length]; int y; for (y = 0; y < row.length; y++) { row[y]
     * * = rpmToTacMS(rpmscale[y]); } return row; }
     */

    private int[] formatTacRowforRPM() {
        int[] row = new int[rpmscale.length];
        int y;

        for (y = 0; y < row.length; y++) {
            row[y] = (int) tacMSToRPM(rpmscale[y]);
        }
        return row;
    }

    /*
     * private double rpmToTacMS(double rpm) { return 120 / (rpm *
     * (double)cylinderCount()); }
     */

    private double tacMSToRPM(final double ms) {
        return ms / (cylinderCount() * MSTORPMFACTOR);
    }

    // Public Data Members

    // private int counter = 0;
    private int /* dwell, i, */cylinderCount = 0;
    private boolean /* isMultiCoil, */tableRequested/* , delay */;
    private double[] rpmscale;
    private int[] mapscale;
    private double[][] advanceTable;
    private MegasquirtMessenger messenger;
    private Stack<Object> outStack = new Stack<Object>();
    private LinkedList<Object> list = new LinkedList<Object>();
    private static final double MSTORPMFACTOR = 120000000;
    private static final double DEGTOMSFACTOR = 166666.6667;
}
